/*
 * Reformer.com Inc.
 * Copyright (c) 20017-2022 All Rights Reserved.
 */
package com.reformer.commons.utils;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * url接口签名类
 *
 * @author sdc
 */
public class Signature {
    private static final Logger LOGGER = LoggerFactory.getLogger(Signature.class);

    /**
     * 签名算法
     *
     * @param signstr 要参与签名的字符串
     * @param key     签名的key
     * @return 签名
     * @throws Exception
     */
    private static String sign(String signstr, String key) {
        signstr += "key=" + key;
        signstr = MD5.encodeMD5(signstr).toUpperCase();
        return signstr;
    }

    /**
     * 签名
     *
     * @param o 要参与签名的数据对象，如为java bean，应重写equals方法
     * @return 签名结果
     */
    public static String getSign(Object o, String key) {
        return sign(createLinkString(o), key);
    }

    private static List<String> createNVP(Object o) {
        ArrayList<String> list = new ArrayList<String>();
        if (o instanceof Map) {
            Map<String, ?> map = (Map<String, ?>) o;
            for (Map.Entry<String, ?> entry : map.entrySet()) {
                String k = entry.getKey();
                Object v = entry.getValue();
                if (StringUtils.isNotEmpty(k) && !k.equals("sign") && !k.equalsIgnoreCase("sign_type") && v != null && v.toString() != "") {
                    if (v instanceof Number || v instanceof Character || v instanceof String || v instanceof Boolean) {
                        list.add(k + "=" + v.toString() + "&");
                    } else {
                        list.addAll(createNVP(v));
                    }
                }
            }
        } else if (o instanceof List) {
            List ls = (List) o;
            if (ls.size() > 1) {
                Collections.sort(ls, new Comparator() {
                    @Override
                    public int compare(Object o1, Object o2) {
                        if (o1 instanceof Map && o2 instanceof Map) {
                            StringBuilder sb1 = new StringBuilder();
                            StringBuilder sb2 = new StringBuilder();
                            Map map1 = (Map) o1;
                            Map map2 = (Map) o2;
                            Set keys = map1.keySet();
                            for (Object k : keys) {
                                sb1.append(map1.get(k));
                                sb2.append(map2.get(k));
                            }
                            return sb1.toString().compareTo(sb2.toString());
                        } else if ((o1 instanceof Number || o1 instanceof Character || o1 instanceof String) && (o2 instanceof Number || o2 instanceof Character || o2 instanceof String)) {
                            return o1.toString().compareTo(o2.toString());
                        } else {
                            return o1.equals(o2) ? 1 : -1;
                        }
                    }
                });
            }
            for (Object item : ls) {
                if (item instanceof Number || item instanceof Character || item instanceof String) {
                    list.add("value=" + item.toString() + "&");
                } else {
                    list.addAll(createNVP(item));
                }
            }
        } else if (o instanceof Number || o instanceof Character || o instanceof String) {
            list.add("value=" + o.toString() + "&");
        } else {
            Class cls = o.getClass();
            Field[] fields = cls.getDeclaredFields();
            try {
                for (Field f : fields) {
                    f.setAccessible(true);
                    if (StringUtils.isNotEmpty(f.getName()) && !f.getName().equals("key") && !f.getName().equalsIgnoreCase("sign_type") && f.get(o) != null && f.get(o) != "") {
                        list.add(f.getName() + "=" + f.get(o) + "&");
                    }
                }
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return list;
    }

    private static String createLinkString(Object o) {
        List<String> list = createNVP(o);
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(arrayToSort[i]);
        }
        return sb.toString();
    }

    /**
     * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
     *
     * @param responseString API返回的JSON数据字符串
     * @return API签名是否合法
     */
    public static boolean checkIsSignValidFromResponseString(String responseString, String key) {
        Map<String, ?> map = JSON.parseObject(responseString);
        return checkSignValid(map, key);
    }

    /**
     * 校验回调API返回的签名
     *
     * @param map
     * @return
     */
    public static boolean checkSignValid(Map<String, ?> map, String key) {
        String signFromAPIResponse = map.get("sign").toString();
        if (signFromAPIResponse == "" || signFromAPIResponse == null) {
            LOGGER.error("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
            return false;
        }
        // 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
        map.remove("sign");
        // 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
        String signForAPIResponse = Signature.getSign(map, key);
        if (!signForAPIResponse.equals(signFromAPIResponse)) {
            // 签名验不过，表示这个API返回的数据有可能已经被篡改了
            LOGGER.error("签名验证失败，有可能被第三方篡改!!!");
            return false;
        }
        return true;
    }
}
